#pragma once
#include "AudioFX.h"

namespace GuitarFX_Audio
{
	using namespace concurrency;

	template <class T>
	class CFrameBuffer
	{
		std::vector<T> m_Data;

		unsigned m_ReadIndex;
		unsigned m_WriteIndex;

		reader_writer_lock m_ReadIndexLock;
		reader_writer_lock m_WriteIndexLock;

	public:

		class CFrameBufferPointer
		{
			unsigned m_IndexBase;
			CFrameBuffer<T> &m_Buffer;
		public:
			CFrameBufferPointer(CFrameBuffer<T> &buffer, unsigned indexBase) : m_Buffer(buffer), m_IndexBase(indexBase)
			{
			}

			T & operator[](int index)
			{
				int dataOffset = index + m_IndexBase;
				if (dataOffset < 0)	// Below zero - offset wrapping over to the end of the buffer
					dataOffset += m_Buffer.m_Data.size();

				if (dataOffset >= (int) m_Buffer.m_Data.size())
					dataOffset -= m_Buffer.m_Data.size();

				// If the offset is less than -1 times buffer length or greater than 2 times buffer lengths
				// The next line is going to break;
				return m_Buffer.m_Data[dataOffset];
			}
		};

		CFrameBuffer() :
			m_ReadIndex(0),
			m_WriteIndex(0)
		{

		}

		CFrameBufferPointer GetPtrAt(int index)
		{
			return CFrameBufferPointer(*this, index);
		}

		CFrameBufferPointer GetReadPtr()
		{
			reader_writer_lock::scoped_lock_read::scoped_lock_read(m_ReadIndexLock);
			return CFrameBufferPointer(*this, m_ReadIndex);
		}

		void Resize(unsigned nSamples)
		{
			reader_writer_lock::scoped_lock::scoped_lock(m_WriteIndexLock);
			reader_writer_lock::scoped_lock::scoped_lock(m_ReadIndexLock);
			m_Data.resize(nSamples,0);
			m_ReadIndex = 0;
			m_WriteIndex = 0;
		}

		void Flush()
		{
			reader_writer_lock::scoped_lock::scoped_lock(m_ReadIndexLock);
			reader_writer_lock::scoped_lock_read::scoped_lock_read(m_WriteIndexLock);
			m_ReadIndex = m_WriteIndex;
		}

		// Lock the read index access and return unread data index and samples available in the buffer
		void GetReadBuffer(unsigned &dataStartIndex, unsigned &nSampleCount)
		{
			m_ReadIndexLock.lock();
			reader_writer_lock::scoped_lock_read::scoped_lock_read(m_WriteIndexLock);

			nSampleCount = (m_WriteIndex >= m_ReadIndex) ? // If write pointer is ahead of read pointer
				m_WriteIndex - m_ReadIndex : // Return difference
				m_Data.size() + m_WriteIndex - m_ReadIndex;  // Indexes are wrapped over the end - fix the difference
			dataStartIndex = m_ReadIndex;
		}
		// Mark specified number of samples read and unlock read index access
		void ReleaseReadBuffer(unsigned nSamplesRead)
		{
			m_ReadIndex += nSamplesRead; 
			if (m_ReadIndex >= m_Data.size())	// Wrap index over the end
				m_ReadIndex = m_ReadIndex - m_Data.size();

			m_ReadIndexLock.unlock();
		}

		// Inserts nFrames from pSource into the buffer. Inserts nGapFrames amount of silence
		void InsertData(LPCVOID pSource, unsigned nSamples, unsigned nGapSamples)
		{
			reader_writer_lock::scoped_lock::scoped_lock(m_WriteIndexLock);

			if (nGapSamples > 0)
			{
				if (nGapSamples + m_WriteIndex <= m_Data.size())
				{
					memset(&m_Data[m_WriteIndex], 0, nGapSamples * sizeof(T));	// Size in bytes
					m_WriteIndex += nGapSamples;	// Size in samples

					if (m_WriteIndex == m_Data.size())	// Handle border case
						m_WriteIndex = 0;
				}
				else
				{
					// Going to wrap over the end of cyclic buffer, memset in two batches
					memset(&m_Data[m_WriteIndex], 0, (m_Data.size() - m_WriteIndex) * sizeof(T)); // Size in bytes
					memset(&m_Data[0], 0, (nGapSamples + m_WriteIndex - m_Data.size()) * sizeof(T)); // Size in bytes
					m_WriteIndex = nGapSamples + m_WriteIndex - m_Data.size(); // Size in samples
				}
			}

			// Now copy the captured frames to the buffer
			if (m_WriteIndex + nSamples <= m_Data.size())
			{
				memcpy(&m_Data[m_WriteIndex], pSource, nSamples * sizeof(T) ); // Size in bytes
				m_WriteIndex += nSamples;

				if (m_WriteIndex == m_Data.size())	// handle border case wrapover
					m_WriteIndex = 0;
			}
			else
			{
				// Going to wrap over the end of cyclic buffer, copy in two batches
				memcpy(&m_Data[m_WriteIndex], pSource, (m_Data.size() - m_WriteIndex) * sizeof(T)); // Size in bytes
				memcpy(&m_Data[0], ((T *) pSource) + (m_Data.size() - m_WriteIndex), // Size in bytes
					(nSamples + m_WriteIndex - m_Data.size()) * sizeof(T));

				m_WriteIndex = nSamples + m_WriteIndex - m_Data.size();	// Size in samples
			}

		}

		void CopyData(LPVOID pDest, unsigned dataStartIndex, unsigned nSamplesToRender)
		{
			// As this copy is not changing read and write indexes no lock is needed
			if (dataStartIndex + nSamplesToRender <= m_Data.size())
			{
				// No wrapping over the end, can do with one copy
				memcpy(pDest, &m_Data[dataStartIndex], nSamplesToRender * sizeof(T)  );
			}
			else
			{
				// Wrapping over the end, do two copies
				memcpy(pDest, &m_Data[dataStartIndex], (m_Data.size() - dataStartIndex) * sizeof(T));
				memcpy(((BYTE *) pDest) + m_Data.size() - dataStartIndex, &m_Data[0], (nSamplesToRender + dataStartIndex - m_Data.size()) * sizeof(T));
			}
		}

	};
};